import re
import flask
import json
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
from functools import partial

from .. import api_client, components, utils
from ..app import app


def _dataset_name_to_task_attr(name):
    if name == 'leftover':
        return 'input_dataset'
    if name == 'loaded':
        return 'result_dataset'
    raise ValueError(f'Not recognised datasaet name {name}')


def _current_task_id():
    url = flask.request.referrer
    task_id_match = re.findall(r'^.*?/tasks/(\d+)$', url)

    if not task_id_match:
        raise ValueError(f'No task id can be extracted from current url {url}')

    return task_id_match[0]


def _fetch_task_detail():
    id = _current_task_id()
    return api_client.retrieve_task(id)


def _fetch_task_dataset(data, name='leftover'):
    return data[_dataset_name_to_task_attr(name)] or []


def _serialize_container(data):
    payload = {
        'start_pos': [0, 0, 0],
        'end_pos': [data['length'], data['width'], data['height']],
        'dimension': [data['length'], data['width'], data['height']],
        'type': data['name']
    }
    return json.dumps(payload)


def _on_dataset_changed(name):
    def handler(state, row, rows):
        sync_db = state == 'idle'
        if not sync_db:
            return row
        
        api_client.update_task(
            id=_current_task_id(),
            payload={
                _dataset_name_to_task_attr(name): rows
            }
        )
        return row
    return handler


content = dbc.Container([
    # Info
    html.Br(),
    html.H2('任务: ', id='task-name-header'),
    html.Div('状态: ', id='task-status-div'),
    html.Div('创建时间: ', id='task-created-div'),
    html.Div('结束时间: ', id='task-finished-div'),


    # Configuration
    html.Br(),

    html.Div(
        html.Label([
            '使用运柜',
            dcc.Dropdown(id='containers-select'),
        ], style={'width': '100%'}),
    ),
    html.Div(
        html.Label([
            '使用算法',
            dcc.Dropdown(id='algorithms-select'),
        ], style={'width': '100%'}),
    ),
    html.Div(
        html.Label([
            'Beam数量',
            dcc.Input(
                value=10,
                type='number',
                id='num-beams-input'
            ),
        ], style={'width': '100%'}),
    ),

    # Control
    html.Br(),
    html.Button('开始装载', id='task-control-btn'),
    html.Button('下载装载结果', id='plan-download-btn'),
    dcc.Download(id='plan-download-control'),

    # Cargoes
    html.Br(),
    components.datatable.new(
        app=app,
        id_prefix='tasks-leftover',
        columns=[
            {'name': '货物名称', 'id': 'name', 'editable': True},
            {'name': '数量', 'id': 'quantity', 'editable': True},
            {'name': '长', 'id': 'length', 'editable': True},
            {'name': '宽', 'id': 'width', 'editable': True},
            {'name': '高', 'id': 'height', 'editable': True},
        ],
        row_deletable=True,
        data_source_store='task-payload',
        data_source_reader=partial(_fetch_task_dataset, name='leftover'),
        on_update=_on_dataset_changed('leftover'),
        on_insert=_on_dataset_changed('leftover'),
        on_delete=_on_dataset_changed('leftover'),
    ),
    # Loaded Cargoes
    dcc.Store(id='task-payload'),
    dcc.Interval(
        id='polling-interval',
        interval=2 * 1000, # in milliseconds
        n_intervals=0
    )
], id='task-detail-page')


@app.callback(
    Output('containers-select', 'options'),
    Output('containers-select', 'value'),
    Output('algorithms-select', 'options'),
    Output('algorithms-select', 'value'),
    Input('task-detail-page', 'children'),
)
def init_config_options(_):
    algo_options = [
        {'label': 'G2LA', 'value': 'algo-002-4'},
        {'label': 'Greedy', 'value': 'algo-002-3'},
    ]
    default_algo = algo_options[0]['value']

    cont_options = [
        {
            'label': f'{x["name"]}: {x["length"]} x {x["width"]} x {x["height"]}',
            'value': _serialize_container(x)
        }
        for x in api_client.list_container()
    ]
    default_cont = cont_options[0]['value']
    return cont_options, default_cont, algo_options, default_algo


@app.callback(
    Output('task-name-header', 'children'),
    Output('task-status-div', 'children'),
    Output('task-created-div', 'children'),
    Output('task-finished-div', 'children'),
    Input('task-payload', 'data'),
)
def set_task_meta_info(data):    
    data = json.loads(data)
    return (
        '任务: ' + data['name'],
        '状态: ' + data['status'],
        '创建时间: ' + str(data['created_at']),
        '结束时间: ' + str(data['finished_at']),
    )


@app.callback(
    Output("plan-download-control", "data"),
    Input("plan-download-btn", "n_clicks"),
    State('task-payload', 'data'),
    prevent_initial_call=True,
)
def download_plan(_, data):
    data = json.loads(data)
    return dict(
        content=json.dumps(data['result_dataset']),
        filename='plan.json'
    )


@app.callback(
    Output('task-payload', 'data'),
    Output('task-control-btn', 'children'),

    Input('task-detail-page', 'children'),
    Input('task-control-btn', 'n_clicks'),
    Input('polling-interval', 'n_intervals'),
    State('task-control-btn', 'children'),
    
    State('task-payload', 'data'),
    State('containers-select', 'value'),
    State('algorithms-select', 'value'),
    State('num-beams-input', 'value'),

)
def set_task_payload_cache(_, n_clicks, n_intervals, signal, curr_data, *config_values):
    ctx = dash.callback_context
    prop_id = ctx.triggered[0]['prop_id'].split('.')[0]

    # Prepare vars
    use_container, use_algo, use_beams_num = config_values
    next_data, next_signal = curr_data, signal

    print(f'[Task Detail] Event fired by {prop_id}. Curr signal {signal}')

    # Init
    if not prop_id:
        next_data = _fetch_task_detail()

    # Submit task
    if prop_id == 'task-control-btn' and signal == '开始装载':
        next_data = api_client.update_task(
            id=_current_task_id(),
            payload={
                'submit': True,
                'config': {
                    'container': json.loads(use_container),
                    'algo_type': use_algo,
                    'num_beams': use_beams_num
                }
            }
        )
        next_signal = '装载中'

    # Task Polling
    if prop_id == 'polling-interval' and curr_data:
        curr_data = json.loads(curr_data)

        if curr_data['status'] in ('submitted', 'running'):
            next_data = _fetch_task_detail()
            next_signal = {
                'submitted': '装载中',
                'running': '装载中',
                'finished': '开始装载',
                'error': '开始装载',
                'idle': '开始装载'
            }[next_data['status']]

        else:
            raise dash.exceptions.PreventUpdate()

    return utils.ensure_json(next_data), next_signal
